/**
 * @file objloadertests.cpp
 * @brief mjn::ObjLoader automated tests implementation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * @author VestniK (Sergey N.Vidyuk) sir.vestnik@gmail.com
 * @date 6 Jul 2010
 */
#include <QtTest/QTest>
#include <QtCore/QObject>
#include <QtCore/QSettings>

#include "defaults.h"
#include "objloader.h"
#include "objloaderprivate.h"
#include "mtlloader.h"
#include "mtlloaderprivate.h"

using namespace mjn;

Q_DECLARE_METATYPE(GeometryObject)

class ObjLoaderTests: public QObject
{
Q_OBJECT
private slots:
    void initTestCase() {
        qRegisterMetaType<GeometryObject>("GeometryObject");
    }

    void tileModelLoaded() {
        ObjLoader loader;
        QString tileModelLocation = QCoreApplication::applicationDirPath() +
                                    "/../client/gui/models/tile.obj";
        QVERIFY2(loader.load(tileModelLocation),
                 loader.errorString().toLocal8Bit().constData());
        QVERIFY2(loader.isValid(),
                 loader.errorString().toLocal8Bit().constData());
    }

    void loadVertex_data() {
        QTest::addColumn<double>("x");
        QTest::addColumn<double>("y");
        QTest::addColumn<double>("z");
        QTest::addColumn<double>("w");
        QTest::addColumn<QString>("vertexString");

        QTest::newRow("3 param") << 0.2 << 0.3 << 0.4 << 1.0
                << "v 0.2 0.3 0.4";
        QTest::newRow("4 param") << 0.2 << 0.3 << 0.4 << 0.5
                << "v 0.2 0.3 0.4 0.5";
        QTest::newRow("tab separator") << 0.2 << 0.3 << 0.4 << 0.5
                << "v\t0.2 \t0.3\t 0.4 \t 0.5";
    }

    void loadVertex() {
        QFETCH(double,x);
        QFETCH(double,y);
        QFETCH(double,z);
        QFETCH(double,w);
        QFETCH(QString,vertexString);

        ObjLoaderPrivate d;
        QVERIFY(d.loadDefinition(vertexString));
        QCOMPARE(d.vertexes.size(),1);
        QCOMPARE(d.vertexes[0].x, x);
        QCOMPARE(d.vertexes[0].y, y);
        QCOMPARE(d.vertexes[0].z, z);
        QCOMPARE(d.vertexes[0].w, w);
    }

    void loadNormal_data() {
        QTest::addColumn<double>("i");
        QTest::addColumn<double>("j");
        QTest::addColumn<double>("k");
        QTest::addColumn<QString>("vertexString");

        QTest::newRow("space separator") << 0.2 << 0.3 << 0.4
                << "vn 0.2 0.3 0.4";
        QTest::newRow("tab separator") << 0.2 << 0.3 << 0.4
                << "vn\t0.2 \t0.3\t 0.4";
    }

    void loadNormal() {
        QFETCH(double,i);
        QFETCH(double,j);
        QFETCH(double,k);
        QFETCH(QString,vertexString);

        ObjLoaderPrivate d;
        QVERIFY(d.loadDefinition(vertexString));
        QCOMPARE(d.normals.size(),1);
        QCOMPARE(d.normals[0].i, i);
        QCOMPARE(d.normals[0].j, j);
        QCOMPARE(d.normals[0].k, k);
    }

    void loadTextureVertex_data() {
        QTest::addColumn<double>("u");
        QTest::addColumn<double>("v");
        QTest::addColumn<double>("w");
        QTest::addColumn<QString>("vertexString");

        QTest::newRow("1 coord") << 0.2 << 0.0 << 0.0
                << "vt 0.2";
        QTest::newRow("2 coord") << 0.2 << 0.3 << 0.0
                << "vt 0.2 0.3";
        QTest::newRow("3 coords") << 0.2 << 0.3 << 0.4
                << "vt 0.2 0.3 0.4";
    }

    void loadTextureVertex() {
        QFETCH(double,u);
        QFETCH(double,v);
        QFETCH(double,w);
        QFETCH(QString,vertexString);

        ObjLoaderPrivate d;
        QVERIFY(d.loadDefinition(vertexString));
        QCOMPARE(d.textureCoords.size(),1);
        QCOMPARE(d.textureCoords[0].u, u);
        QCOMPARE(d.textureCoords[0].v, v);
        QCOMPARE(d.textureCoords[0].w, w);
    }

    void loadSmoothingGroup_data() {
        QTest::addColumn<int>("s");
        QTest::addColumn<QString>("definition");

        QTest::newRow("0") << 0 << "s 0";
        QTest::newRow("off") << 0 << "s off";
        QTest::newRow("1") << 1 << "s 1";
    }

    void loadSmoothingGroup() {
        QFETCH(int,s);
        QFETCH(QString,definition);

        ObjLoaderPrivate d;
        QCOMPARE(d.currentSmoothingGroup, 0);
        QVERIFY(d.loadDefinition(definition));
        QCOMPARE(d.currentSmoothingGroup, s);
    }

    void loadMaterial_data() {
        QTest::addColumn<QString>("mat");
        QTest::addColumn<QString>("definition");

        QTest::newRow("qwe") << "qwe" << "usemtl qwe";
        QTest::newRow("rty") << "rty" << "usemtl rty";
    }

    void loadMaterial() {
        QFETCH(QString,mat);
        QFETCH(QString,definition);

        ObjLoaderPrivate d;
        QCOMPARE(d.currentMaterial, QString());
        QVERIFY(d.loadDefinition(definition));
        QCOMPARE(d.currentMaterial, mat);
    }

    void loadGeometryObj_data() {
        QTest::addColumn<bool>("isSmooth");
        QTest::addColumn<bool>("useMaterial");
        QTest::addColumn<GeometryObject>("object");
        QTest::addColumn<QString>("definition");

        GeometryObject go;

        go.type = GeometryObject::Points;
        go.vertexRefs.append(VertexRef(1,1,1));
        QTest::newRow("(a) 1 point") << false << false << go
                << "p 1/1/1";
        QTest::newRow("(b) 1 point") << false << true << go
                << "p 1/1/1";
        QTest::newRow("(c) 1 point") << true << false << go
                << "p 1/1/1";
        QTest::newRow("(d) 1 point") << true << true << go
                << "p 1/1/1";

        go = GeometryObject();
        go.type = GeometryObject::Points;
        go.vertexRefs.append(VertexRef(1,1,1));
        go.vertexRefs.append(VertexRef(2,3,2));
        QTest::newRow("(a) 2 points") << false << false << go
                << "p 1/1/1 2/3/2";
        QTest::newRow("(b) 2 points") << false << true << go
                << "p 1/1/1 2/3/2";
        QTest::newRow("(c) 2 points") << true << false << go
                << "p 1/1/1 2/3/2";
        QTest::newRow("(d) 2 points") << true << true << go
                << "p 1/1/1 2/3/2";

        go = GeometryObject();
        go.type = GeometryObject::Lines;
        go.vertexRefs.append(VertexRef(1,3,4));
        go.vertexRefs.append(VertexRef(2,1,2));
        QTest::newRow("(a) 1 line") << false << false << go
                << "l 1/3/4 2/1/2";
        QTest::newRow("(b) 1 line") << false << true << go
                << "l 1/3/4 2/1/2";
        QTest::newRow("(c) 1 line") << true << false << go
                << "l 1/3/4 2/1/2";
        QTest::newRow("(d) 1 line") << true << true << go
                << "l 1/3/4 2/1/2";

        go = GeometryObject();
        go.type = GeometryObject::Lines;
        go.vertexRefs.append(VertexRef(1,3,4));
        go.vertexRefs.append(VertexRef(2,1,2));
        go.vertexRefs.append(VertexRef(3,2,3));
        QTest::newRow("(a) 2 lines") << false << false << go
                << "l 1/3/4 2/1/2 3/2/3";
        QTest::newRow("(b) 2 lines") << false << true << go
                << "l 1/3/4 2/1/2 3/2/3";
        QTest::newRow("(c) 2 lines") << true << false << go
                << "l 1/3/4 2/1/2 3/2/3";
        QTest::newRow("(d) 2 lines") << true << true << go
                << "l 1/3/4 2/1/2 3/2/3";

        go = GeometryObject();
        go.type = GeometryObject::Face;
        go.vertexRefs.append(VertexRef(1,3,4));
        go.vertexRefs.append(VertexRef(2,1,2));
        go.vertexRefs.append(VertexRef(3,2,3));
        QTest::newRow("(a) textured oriented triangle") << false << false << go
                << "f 1/3/4 2/1/2 3/2/3";
        QTest::newRow("(b) textured oriented triangle") << false << true << go
                << "f 1/3/4 2/1/2 3/2/3";
        QTest::newRow("(c) textured oriented triangle") << true << false << go
                << "f 1/3/4 2/1/2 3/2/3";
        QTest::newRow("(d) textured oriented triangle") << true << true << go
                << "f 1/3/4 2/1/2 3/2/3";

        go = GeometryObject();
        go.type = GeometryObject::Face;
        go.vertexRefs.append(VertexRef(1,0,2));
        go.vertexRefs.append(VertexRef(2,0,2));
        go.vertexRefs.append(VertexRef(3,0,2));
        QTest::newRow("(a) oriented triangle") << false << false << go
                << "f 1//2 2//2 3//2";
        QTest::newRow("(b) oriented triangle") << false << true << go
                << "f 1//2 2//2 3//2";
        QTest::newRow("(c) oriented triangle") << true << false << go
                << "f 1//2 2//2 3//2";
        QTest::newRow("(d) oriented triangle") << true << true << go
                << "f 1//2 2//2 3//2";

        go = GeometryObject();
        go.type = GeometryObject::Face;
        go.vertexRefs.append(VertexRef(1,3,0));
        go.vertexRefs.append(VertexRef(2,1,0));
        go.vertexRefs.append(VertexRef(3,2,0));
        QTest::newRow("(a) textured not oriented triangle") << false << false << go
                << "f 1/3/ 2/1/ 3/2/";
        QTest::newRow("(b) textured not oriented triangle") << false << true << go
                << "f 1/3/ 2/1/ 3/2/";
        QTest::newRow("(c) textured not oriented triangle") << true << false << go
                << "f 1/3/ 2/1/ 3/2/";
        QTest::newRow("(d) textured not oriented triangle") << true << true << go
                << "f 1/3/ 2/1/ 3/2/";

        go = GeometryObject();
        go.type = GeometryObject::Face;
        go.vertexRefs.append(VertexRef(1,0,0));
        go.vertexRefs.append(VertexRef(2,0,0));
        go.vertexRefs.append(VertexRef(3,0,0));
        QTest::newRow("(a) triangle") << false << false << go
                << "f 1// 2// 3//";
        QTest::newRow("(b) triangle") << false << true << go
                << "f 1// 2// 3//";
        QTest::newRow("(c) triangle") << true << false << go
                << "f 1// 2// 3//";
        QTest::newRow("(d) triangle") << true << true << go
                << "f 1// 2// 3//";

        go = GeometryObject();
        go.type = GeometryObject::Face;
        go.vertexRefs.append(VertexRef(1,3,4));
        go.vertexRefs.append(VertexRef(2,1,2));
        go.vertexRefs.append(VertexRef(3,2,3));
        go.vertexRefs.append(VertexRef(4,4,1));
        QTest::newRow("(a) quad") << false << false << go
                << "f 1/3/4 2/1/2 3/2/3 4/4/1";
        QTest::newRow("(b) quad") << false << true << go
                << "f 1/3/4 2/1/2 3/2/3 4/4/1";
        QTest::newRow("(c) quad") << true << false << go
                << "f 1/3/4 2/1/2 3/2/3 4/4/1";
        QTest::newRow("(d) quad") << true << true << go
                << "f 1/3/4 2/1/2 3/2/3 4/4/1";
    }

    void loadGeometryObj() {
        QFETCH(bool,isSmooth);
        QFETCH(bool,useMaterial);
        QFETCH(GeometryObject,object);
        QFETCH(QString,definition);

        ObjLoaderPrivate d;
        for (int i=0; i<4; i++) {
            d.vertexes.append(Vertex());
            d.normals.append(VertexNormal());
            d.textureCoords.append(TextureCoord());
        }

        int s;
        QString mtl;
        if (useMaterial) {
            d.loadDefinition("usemtl qwe");
            mtl = "qwe";
        } else {
            mtl = QString();
        }
        if (isSmooth) {
            d.loadDefinition("s 1");
            s = 1;
        } else {
            s = 0;
        }

        QCOMPARE(d.geometryObjects.size(), 0);
        QVERIFY(d.loadDefinition(definition));
        QCOMPARE(d.geometryObjects.size(), 1);
        QVERIFY(d.geometryObjects.contains(s));
        QCOMPARE(d.geometryObjects[s].size(),1);
        QVERIFY(d.geometryObjects[s].contains(mtl));
        QCOMPARE(d.geometryObjects[s][mtl].size(), 1);

        GeometryObject loaded = d.geometryObjects[s][mtl].first();
        QCOMPARE(loaded.type, object.type);
        QCOMPARE(loaded.vertexRefs.size(), object.vertexRefs.size());
        for(int i=0; i<loaded.vertexRefs.size(); i++) {
            QCOMPARE(loaded.vertexRefs[i].vertex, object.vertexRefs[i].vertex);
            QCOMPARE(loaded.vertexRefs[i].normal, object.vertexRefs[i].normal);
            QCOMPARE(loaded.vertexRefs[i].textureCoord, object.vertexRefs[i].textureCoord);
        }
    }

    void loadMaterialReflectivities_data()
    {
        QTest::addColumn<double>("r");
        QTest::addColumn<double>("g");
        QTest::addColumn<double>("b");
        QTest::addColumn<QString>("definition");

        QTest::newRow("Ka") << 0.1 << 0.2 << 0.3 << "Ka 0.1 0.2 0.3";
        QTest::newRow("Kd") << 0.125 << 0.333 << 0.4 << "Kd 0.125 0.333 0.4";
        QTest::newRow("Ks") << 0.231 << 0.452 << 0.673 << "Ks 0.231 0.452 0.673";
        QTest::newRow("tabs separator") << 0.231 << 0.452 << 0.673 << "Ks \t0.231\t0.452\t 0.673";
    }

    void loadMaterialReflectivities()
    {
        QFETCH(double,r);
        QFETCH(double,g);
        QFETCH(double,b);
        QFETCH(QString,definition);

        MtlLoaderPrivate d;
        d.loadDefinition("newmtl test");
        QCOMPARE(d.currentMaterial, QString("test"));

        double *color;
        if (definition.startsWith("Ka")) {
            color = d.materials[d.currentMaterial].ambientReflectivity;
        } else if (definition.startsWith("Kd")) {
            color = d.materials[d.currentMaterial].diffuseReflectivity;
        } else if (definition.startsWith("Ks")) {
            color = d.materials[d.currentMaterial].specularReflectivity;
        }

        QVERIFY(d.loadDefinition(definition));
        QVERIFY(d.materials.contains(d.currentMaterial));
        QCOMPARE(color[0], r);
        QCOMPARE(color[1], g);
        QCOMPARE(color[2], b);
    }

    void loadMaterialAlpha_data()
    {
        QTest::addColumn<double>("alpha");
        QTest::addColumn<QString>("definition");

        QTest::newRow("d") << 0.7 << "d 0.7";
        QTest::newRow("Ts") << 0.45 << "Ts 0.45";
        QTest::newRow("d tabs") << 0.123 << "d\t 0.123";
        QTest::newRow("Ts tabs") << 0.45567 << "Ts \t0.45567";
    }

    void loadMaterialAlpha()
    {
        QFETCH(double,alpha);
        QFETCH(QString,definition);

        MtlLoaderPrivate d;
        d.loadDefinition("newmtl test");
        QCOMPARE(d.currentMaterial, QString("test"));

        QVERIFY(d.loadDefinition(definition));
        QVERIFY(d.materials.contains(d.currentMaterial));
        QCOMPARE(d.materials[d.currentMaterial].alpha, alpha);
    }
};

QTEST_MAIN(ObjLoaderTests)

#include "objloadertests.moc"
